---
title: "Example: Human in the Loop | Workflows | Kastrax Docs"
description: Example of using Kastrax to create workflows with human intervention points.
---

import { GithubLink } from '@/components/github-link';

# Human in the Loop Workflow ✅

Human-in-the-loop workflows allow you to pause execution at specific points to collect user input, make decisions, or perform actions that require human judgment.
This example demonstrates how to create a workflow with human intervention points.


## Define Agents ✅
Define the travel agents.

```ts showLineNumbers copy filename="agents/travel-agents.ts"
import { Agent } from '@kastrax/core/agent'
import { openai } from '@ai-sdk/openai'

const llm = openai('gpt-4o')

export const summaryAgent = new Agent({
  name: 'summaryTravelAgent',
  model: llm,
  instructions: `
  You are a travel agent who is given a user prompt about what kind of holiday they want to go on.
  You then generate 3 different options for the holiday. Return the suggestions as a JSON array {"location": "string", "description": "string"}[]. Don't format as markdown.

  Make the options as different as possible from each other.
  Also make the plan very short and summarized.
  `,
})
export const travelAgent = new Agent({
  name: 'travelAgent',
  model: llm,
  instructions: `
  You are a travel agent who is given a user prompt about what kind of holiday they want to go on. A summary of the plan is provided as well as the location.
  You then generate a detailed travel plan for the holiday.
  `,
})
```

## Define Suspendable workflow ✅
Defines a workflow which includes a suspending step: `humanInputStep`.

```ts showLineNumbers copy filename="workflows/human-in-the-loop-workflow.ts"
import { createWorkflow, createStep } from '@kastrax/core/workflows/vNext'

import { z } from 'zod'

const generateSuggestionsStep = createStep({
  id: 'generate-suggestions',
  inputSchema: z.object({
    vacationDescription: z.string().describe('The description of the vacation'),
  }),
  outputSchema: z.object({
    suggestions: z.array(z.string()),
    vacationDescription: z.string(),
  }),
  execute: async ({ inputData, kastrax, container }) => {
    if (!kastrax) {
      throw new Error('Kastrax is not initialized')
    }

    const { vacationDescription } = inputData
    const result = await kastrax.getAgent('summaryTravelAgent').generate([
      {
        role: 'user',
        content: vacationDescription,
      },
    ])
    console.log(result.text)
    return { suggestions: JSON.parse(result.text), vacationDescription }
  },
})

const humanInputStep = createStep({
  id: 'human-input',
  inputSchema: z.object({
    suggestions: z.array(z.string()),
    vacationDescription: z.string(),
  }),
  outputSchema: z.object({
    selection: z.string().describe('The selection of the user'),
    vacationDescription: z.string(),
  }),
  resumeSchema: z.object({
    selection: z.string().describe('The selection of the user'),
  }),
  suspendSchema: z.object({
    suggestions: z.array(z.string()),
  }),
  execute: async ({ inputData, resumeData, suspend, getInitData }) => {
    if (!resumeData?.selection) {
      await suspend({ suggestions: inputData?.suggestions })
      return {
        selection: '',
        vacationDescription: inputData?.vacationDescription,
      }
    }

    return {
      selection: resumeData?.selection,
      vacationDescription: inputData?.vacationDescription,
    }
  },
})

const travelPlannerStep = createStep({
  id: 'travel-planner',
  inputSchema: z.object({
    selection: z.string().describe('The selection of the user'),
    vacationDescription: z.string(),
  }),
  outputSchema: z.object({
    travelPlan: z.string(),
  }),
  execute: async ({ inputData, kastrax }) => {
    const travelAgent = kastrax?.getAgent('travelAgent')
    if (!travelAgent) {
      throw new Error('Travel agent is not initialized')
    }

    const { selection, vacationDescription } = inputData
    const result = await travelAgent.generate([
      { role: 'assistant', content: vacationDescription },
      { role: 'user', content: selection || '' },
    ])
    console.log(result.text)
    return { travelPlan: result.text }
  },
})

const weatherWorkflow = createWorkflow({
  id: 'weather-workflow',
  inputSchema: z.object({
    vacationDescription: z.string().describe('The description of the vacation'),
  }),
  outputSchema: z.object({
    travelPlan: z.string(),
  }),
})
  .then(generateSuggestionsStep)
  .then(humanInputStep)
  .then(travelPlannerStep)

travelAgentWorkflow.commit()

export { weatherWorkflow, humanInputStep }
```

## Register Agent and Workflow instances with Kastrax class ✅
Register the agents and the weather workflow with the kastrax instance.
This is critical for enabling access to the agents within the workflow.

```ts showLineNumbers copy filename="index.ts"
import { Kastrax } from '@kastrax/core/kastrax'
import { createLogger } from '@kastrax/core/logger'
import { weatherWorkflow } from './workflows'
import { travelAgent, summaryAgent } from './agents'

const kastrax = new Kastrax({
  vnext_workflows: {
    weatherWorkflow,
  },
  agents: {
    travelAgent,
	  summaryAgent
  },
  logger: createLogger({
    name: 'Kastrax',
    level: 'info',
  }),
})

export { kastrax }
```

## Execute the suspendable weather workflow ✅
Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData.
In addition to this, we'll resume the `humanInputStep` after collecting user input with the readline package.

```ts showLineNumbers copy filename="exec.ts"
import { kastrax } from "./"
import { createInterface } from 'readline'
import { humanInputStep } from './workflows'

const workflow = kastrax.vnext_getWorkflow('weather-workflow')
const run = workflow.createRun({})
const result = await run.start({
  inputData: { vacationDescription: 'I want to go to the beach' },
})

console.log('result', result)

const suggStep = result?.steps?.['generate-suggestions']

if (suggStep.status === 'success') {
  const suggestions = suggStep.output?.suggestions
  console.log(
    suggestions
      .map(({ location, description }) => `- ${location}: ${description}`)
      .join('\n')
  )

  const userInput = await readInput()

  console.log('Selected:', userInput)

  console.log('resuming from', result, 'with', {
    inputData: {
      selection: userInput,
      vacationDescription: 'I want to go to the beach',
      suggestions: suggStep?.output?.suggestions,
    },
    step: humanInputStep,
  })

  const result2 = await run.resume({
    resumeData: {
      selection: userInput,
      vacationDescription: 'I want to go to the beach',
      suggestions: suggStep?.output?.suggestions,
    },
    step: humanInputStep,
  })

  console.dir(result2, { depth: null })
}
```

Human-in-the-loop workflows are powerful for building systems that blend automation with human judgment, such as:
- Content moderation systems
- Approval workflows
- Supervised AI systems
- Customer service automation with escalation

<br />
<br />
<hr className={"dark:border-[#404040] border-gray-300"} />
<br />
<br />
<GithubLink
  link={
    "https://github.com/kastrax-ai/kastrax/blob/main/examples/basics/workflows/human-in-the-loop"
  }
/>

